package tilegame;

import java.awt.*;
import java.awt.event.KeyEvent;
import java.util.Iterator;

import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.sampled.AudioFormat;

import graphics.*;
import sound.*;
import input.*;
import test.GameCore;
import tilegame.sprites.*;

/**
GameManager manages all parts of the game.
 */
public class GameManager extends GameCore {

    public static void main(String[] args) {
        new GameManager().run();
    }
    // uncompressed, 44100Hz, 16-bit, mono, signed, little-endian
    private static final AudioFormat PLAYBACK_FORMAT =
            new AudioFormat(44100, 16, 1, true, false);
    private static final int DRUM_TRACK = 1;
    public static final float GRAVITY = 0.002f;
    private Point pointCache = new Point();
    private TileMap map;
    private MidiPlayer midiPlayer;
    private SoundManager soundManager;
    private ResourceManager resourceManager;
    private Sound prizeSound;
    private Sound deadSound;
    private Sound boopSound;
    private Sound introSound;
    private Sound backgroundMusic;
    private Sound backgroundMusic2;
    private InputManager inputManager;
    private TileMapRenderer renderer;
    private GameAction moveLeft;
    private GameAction moveRight;
    private GameAction moveUp;
    private GameAction moveDown;
    private GameAction shoot;
    private GameAction exit;
    private GameAction pause;
    private Animation introAnim;
    private boolean isPlaying;

    public void init() {
        isPlaying = false;
        super.init();

        // set up input manager
        initInput();

        // start resource manager
        resourceManager = new ResourceManager(
                screen.getFullScreenWindow().getGraphicsConfiguration());

        exit = new GameAction("exit",
                GameAction.DETECT_INITAL_PRESS_ONLY);
        inputManager.mapToKey(exit, KeyEvent.VK_ESCAPE);

        // start music
        midiPlayer = new MidiPlayer();
        Sequence introSequence = midiPlayer.getSequence("sounds/intro.midi");

        introAnim = new Animation();
        introAnim.addFrame(resourceManager.loadImage("intro_a.png"), 2000);
        introAnim.addFrame(resourceManager.loadImage("intro_b.png"), 4000);
        introAnim.addFrame(resourceManager.loadImage("intro_c.png"), 4000);
        introAnim.addFrame(resourceManager.loadImage("intro_d.png"), 3000);
        introAnim.addFrame(resourceManager.loadImage("intro_e.png"), 3000);
        introAnim.addFrame(resourceManager.loadImage("intro_f.png"), 3000);
        introAnim.addFrame(resourceManager.loadImage("intro_g.png"), 3000);
        introAnim.addFrame(resourceManager.loadImage("intro_h.png"), 5000);
        introAnim.addFrame(resourceManager.loadImage("intro_i.png"), 4000);
        introAnim.addFrame(resourceManager.loadImage("intro_j.png"), 7000);
        introAnim.addFrame(resourceManager.loadImage("intro_k.png"), 7000);
        introAnim.addFrame(resourceManager.loadImage("intro_l.png"), 7000);
        introAnim.addFrame(resourceManager.loadImage("intro_m.png"), 10000);

        midiPlayer.play(introSequence, true);

        introAnimationLoop();

        midiPlayer.stop();

        // load resources
        renderer = new TileMapRenderer();
        renderer.setBackground(
                resourceManager.loadImage("background.png"));
        renderer.setUpperDashboard(
                resourceManager.loadImage("upperDashboard.png"));
        renderer.setLowerDashboard(
                resourceManager.loadImage("lowerDashboard.png"));
        renderer.setPlayerLife(
                resourceManager.loadImage("life1.png"));
        renderer.setLifeMeter(resourceManager.loadImage("lifeMeter.png"));

        // load first map
        map = resourceManager.loadNextMap();

        // load sounds
        soundManager = new SoundManager(PLAYBACK_FORMAT);
        prizeSound = soundManager.getSound("sounds/prize.wav");

        Sequence sequence =
                midiPlayer.getSequence("sounds/music1.midi");
        midiPlayer.play(sequence, true);
        toggleDrumPlayback();
        setPaused(true);
    }

    public void introAnimationLoop() {
        long startTime = System.currentTimeMillis();
        long currTime = startTime;

        while (currTime - startTime < 61000) {

            if (exit.isPressed()) {
                break;
            }

            long elapsedTime =
                    System.currentTimeMillis() - currTime;
            currTime += elapsedTime;

            // update animation
            introAnim.update(elapsedTime);

            // draw and update screen
            Graphics2D g = screen.getGraphics();
            draw(g);
            g.dispose();
            screen.update();

            // take a nap
            try {
                Thread.sleep(20);
            } catch (InterruptedException ex) {
            }
        }

        isPlaying = true;
    }

    /**
    Closes any resources used by the GameManager.
     */
    public void stop() {
        super.stop();
        //midiPlayer.close();
        soundManager.close();
    }

    private void initInput() {
        moveLeft = new GameAction("moveLeft");
        moveRight = new GameAction("moveRight");
        moveUp = new GameAction("moveUp");
        moveDown = new GameAction("moveDown");
        shoot = new GameAction("shoot", GameAction.DETECT_INITAL_PRESS_ONLY);
        pause = new GameAction("pause",
                GameAction.DETECT_INITAL_PRESS_ONLY);

        inputManager = new InputManager(
                screen.getFullScreenWindow());
        inputManager.setCursor(InputManager.INVISIBLE_CURSOR);

        inputManager.mapToKey(moveLeft, KeyEvent.VK_LEFT);
        inputManager.mapToKey(moveRight, KeyEvent.VK_RIGHT);
        inputManager.mapToKey(moveUp, KeyEvent.VK_UP);
        inputManager.mapToKey(moveDown, KeyEvent.VK_DOWN);
        inputManager.mapToKey(shoot, KeyEvent.VK_SPACE);
        inputManager.mapToKey(pause, KeyEvent.VK_ENTER);
    }

    /**
    Sets the paused state.
     */
    public void setPaused(boolean p) {
        if (paused != p) {
            this.paused = p;
            inputManager.resetAllGameActions();
        }
    }
    private int direction = 1;

    private void checkInput(long elapsedTime) {
        if (pause.isPressed()) {
            if (paused) {
                renderer.setLowerDashboard(null);
            } else {
                renderer.setLowerDashboard(resourceManager.loadImage("lowerDashboard.png"));
            }
            setPaused(!isPaused());
            return;
        }

        Player player = (Player) map.getPlayer();
        if (player.isAlive()) {
            float velocityX = 0;
            float velocityY = 0;

            if (moveLeft.isPressed()) {
                velocityX -= player.getMaxSpeed();
                direction = -1;
            }
            if (moveRight.isPressed()) {
                velocityX += player.getMaxSpeed();
                direction = 1;
            }
            if (moveUp.isPressed()) {
                velocityY -= player.getMaxSpeed();
            }
            if (moveDown.isPressed()) {
                velocityY += player.getMaxSpeed();
            }
            if (shoot.isPressed()) {
                resourceManager.generateShot(map, (int) player.getX() + (direction * -15),
                        (int) player.getY(), direction);
            }

            player.setVelocityX(velocityX);
            player.setVelocityY(velocityY);
        }

    }
    private boolean isFinished;
    private boolean isGameStarted;

    public void draw(Graphics2D g) {
        if (isPlaying) {
            if (!isGameStarted) {
                g.drawImage(resourceManager.loadImage("title.png"), 0, 0, screen.getWidth(), screen.getHeight(), null);
                g.drawString("Press Enter to Play", (screen.getWidth() / 2.5f), 100);
                return;
            }

            renderer.draw(g, map,
                    screen.getWidth(), screen.getHeight());
        } else {
            g.drawImage(introAnim.getImage(), 0, 0, screen.getWidth(), screen.getHeight(), null);
            g.drawString("Press ESC to Skip Intro...", screen.getWidth() / 1.6f, screen.getHeight() - 10);
        }

        if (isFinished) {
            g.drawImage(resourceManager.loadImage("congratz.png"), 0, 0, screen.getWidth(), screen.getHeight(), null);
            g.drawString("Press ESC to Exit...", screen.getWidth() / 1.6f, screen.getHeight() - 10);
        }
    }

    /**
    Gets the current map.
     */
    public TileMap getMap() {
        return map;
    }

    /**
    Turns on/off drum playback in the midi music (track 1).
     */
    public void toggleDrumPlayback() {
        Sequencer sequencer = midiPlayer.getSequencer();
        if (sequencer != null) {
            sequencer.setTrackMute(DRUM_TRACK,
                    !sequencer.getTrackMute(DRUM_TRACK));
        }
    }

    /**
    Gets the tile that a Sprites collides with. Only the
    Sprite's X or Y should be changed, not both. Returns null
    if no collision is detected.
     */
    public Point getTileCollision(Sprite sprite,
            float newX, float newY) {
        float fromX = Math.min(sprite.getX(), newX);
        float fromY = Math.min(sprite.getY(), newY);
        float toX = Math.max(sprite.getX(), newX);
        float toY = Math.max(sprite.getY(), newY);

        // get the tile locations
        int fromTileX = TileMapRenderer.pixelsToTiles(fromX);
        int fromTileY = TileMapRenderer.pixelsToTiles(fromY);
        int toTileX = TileMapRenderer.pixelsToTiles(
                toX + sprite.getWidth() - 1);
        int toTileY = TileMapRenderer.pixelsToTiles(
                toY + sprite.getHeight() - 1);

        // check each tile for a collision
        for (int x = fromTileX; x <= toTileX; x++) {
            for (int y = fromTileY; y <= toTileY; y++) {
                if (x < 0 || x >= map.getWidth()
                        || map.getTile(x, y) != null || y < 0) {
                    // collision found, return the tile
                    pointCache.setLocation(x, y);
                    return pointCache;
                }
            }
        }

        // no collision found
        return null;
    }

    /**
    Checks if two Sprites collide with one another. Returns
    false if the two Sprites are the same. Returns false if
    one of the Sprites is a Creature that is not alive.
     */
    public boolean isCollision(Sprite s1, Sprite s2) {
        // if the Sprites are the same, return false
        if (s1 == s2) {
            return false;
        }

        if ((s1 instanceof Shot) && (s2 instanceof Shot)) {
            return false;
        }

        // if one of the Sprites is a dead Creature, return false
        if (s1 instanceof Element && !((Element) s1).isAlive()) {
            return false;
        }
        if (s2 instanceof Element && !((Element) s2).isAlive()) {
            return false;
        }

        // get the pixel location of the Sprites
        int s1x = Math.round(s1.getX());
        int s1y = Math.round(s1.getY());
        int s2x = Math.round(s2.getX());
        int s2y = Math.round(s2.getY());

        // check if the two sprites' boundaries intersect
        return (s1x < s2x + s2.getWidth()
                && s2x < s1x + s1.getWidth()
                && s1y < s2y + s2.getHeight()
                && s2y < s1y + s1.getHeight());
    }

    /**
    Gets the Sprite that collides with the specified Sprite,
    or null if no Sprite collides with the specified Sprite.
     */
    public Sprite getSpriteCollision(Sprite sprite) {

        // run through the list of Sprites
        Iterator i = map.getSprites();
        while (i.hasNext()) {
            Sprite otherSprite = (Sprite) i.next();
            if (isCollision(sprite, otherSprite)) {
                // collision found, return the Sprite
                return otherSprite;
            }
        }

        // no collision found
        return null;
    }
    private boolean paused;

    private boolean isPaused() {
        return paused;
    }

    /**
    Updates Animation, position, and velocity of all Sprites
    in the current map.
     */
    public void update(long elapsedTime) {
        if (exit.isPressed()) {
            stop();
        }

        if (!isGameStarted) {
            if (pause.isPressed()) {
                isGameStarted = true;
            }

            // draw and update screen
            Graphics2D g = screen.getGraphics();
            draw(g);
            g.dispose();
            screen.update();
            return;
        }

        if (!isFinished) {
            Element player = (Element) map.getPlayer();

            // player is dead! start map over
            if (player.getState() == Element.STATE_DEAD) {
                map = resourceManager.reloadMap();
                return;
            }

            // get keyboard/mouse input
            checkInput(elapsedTime);

            if (!isPaused()) {
                // update player
                updateElement(player, elapsedTime);
                player.update(elapsedTime);

                // update other sprites
                Iterator i = map.getSprites();
                while (i.hasNext()) {
                    Sprite sprite = (Sprite) i.next();
                    if (sprite instanceof Element) {
                        Element creature = (Element) sprite;
                        if (creature.getState() == Element.STATE_DEAD) {
                            i.remove();
                            if (creature instanceof Girl) {
                                GameCore.POINTS += 230;
                            } else if (creature instanceof Boy) {
                                GameCore.POINTS += 460;
                            } else if (creature instanceof Negro) {
                                GameCore.POINTS += 920;
                            } else if (creature instanceof Boss) {
                                GameCore.POINTS += 1840;
                            }
                        } else {
                            if (creature instanceof Negro) {
                                if (creature.isAlive() && creature.getVelocityY() == 0) {
                                    ((Negro) creature).jump(true);
                                }
                            }

                            if (creature instanceof Boss) {
                                if (creature.isAlive() && creature.getVelocityY() == 0) {
                                    ((Boss) creature).jump(true);
                                }
                            }

                            updateElement(creature, elapsedTime);
                        }
                    }
                    // normal update
                    sprite.update(elapsedTime);
                }
            }
        } else {
            // draw and update screen
            Graphics2D g = screen.getGraphics();
            draw(g);
            g.dispose();
            screen.update();
        }
    }

    /**
    Updates the creature, applying gravity for creatures that
    aren't flying, and checks collisions.
     */
    private void updateElement(Element element,
            long elapsedTime) {

        // apply gravity
        if (!element.isFlying()) {
            element.setVelocityY(element.getVelocityY()
                    + GRAVITY * elapsedTime);
        }

        // change x
        float dx = element.getVelocityX();
        float oldX = element.getX();
        float newX = oldX + dx * elapsedTime;
        Point tile =
                getTileCollision(element, newX, element.getY());
        if (tile == null) {
            element.setX(newX);
        } else {
            // line up with the tile boundary
            if (dx > 0) {
                element.setX(
                        TileMapRenderer.tilesToPixels(tile.x)
                        - element.getWidth());
            } else if (dx < 0) {
                element.setX(
                        TileMapRenderer.tilesToPixels(tile.x + 1));
            }
            element.collideHorizontal();
        }
        if (element instanceof Player) {
            checkPlayerCollision((Player) element, false);
        }
        if (element instanceof Shot) {
            checkShotCollision((Shot) element);
        }

        // change y
        float dy = element.getVelocityY();
        float oldY = element.getY();
        float newY = oldY + dy * elapsedTime;
        tile = getTileCollision(element, element.getX(), newY);
        if (tile == null) {
            element.setY(newY);
        } else {
            // line up with the tile boundary
            if (dy >= 0) {
                element.setY(
                        TileMapRenderer.tilesToPixels(tile.y)
                        - element.getHeight());
            } else if (dy <= 0) {
                element.setY(
                        TileMapRenderer.tilesToPixels(tile.y + 1));
            }
            element.collideVertical();
        }
        if (element instanceof Player) {
            boolean canKill = (oldY < element.getY());
            checkPlayerCollision((Player) element, canKill);
        }
        if (element instanceof Shot) {
            checkShotCollision((Shot) element);
        }

    }

    public void checkShotCollision(Shot shot) {
        Sprite collisionSprite = getSpriteCollision(shot);

        if (collisionSprite != null) {
            if (collisionSprite instanceof Element) {
                Element badguy = (Element) collisionSprite;
                shot.setState(Player.STATE_DYING);
                badguy.addLife(-((Player) map.getPlayer()).getStrength());
                soundManager.play(boopSound);
                GameCore.POINTS += 3;
            }
        }
    }

    /**
    Checks for Player collision with other Sprites. If
    canKill is true, collisions with Creatures will kill
    them.
     */
    public void checkPlayerCollision(Player player,
            boolean canKill) {
        if (!player.isAlive()) {
            return;
        }

        // check for player collision with other sprites
        Sprite collisionSprite = getSpriteCollision(player);
        if (collisionSprite instanceof PowerUp) {
            acquirePowerUp((PowerUp) collisionSprite);
        } else if (collisionSprite instanceof Element && !(collisionSprite instanceof Shot)) {
            Element badguy = (Element) collisionSprite;
            if (canKill) {
                // kill the badguy and make player bounce
                soundManager.play(boopSound);
                inputManager.resetAllGameActions();
                player.setY(player.getY() - screen.getHeight() / 10);

                badguy.addLife(-player.getStrength());
            } else {
                inputManager.resetAllGameActions();

                if (player.getX() > badguy.getX()) {
                    player.setX(player.getX() + screen.getWidth() / 100);
                    //player.setX(TileMapRenderer.pixelsToTiles(player.getX()) + 1);
                } else {
                    player.setX(player.getX() - screen.getWidth() / 100);
                    //player.setX(TileMapRenderer.pixelsToTiles(player.getX()) - 1);
                }

                player.addLife(-1);

                if (!player.isAlive()) {
                    shoot = new GameAction("shoot", GameAction.DETECT_INITAL_PRESS_ONLY);
                    player.setStrength(1);
                    inputManager.mapToKey(shoot, KeyEvent.VK_SPACE);
                    soundManager.play(deadSound);
                }
            }
        }
    }

    /**
    Gives the player the speicifed power up and removes it
    from the map.
     */
    public void acquirePowerUp(PowerUp powerUp) {
        // remove it from the map
        map.removeSprite(powerUp);

        if (powerUp instanceof PowerUp.WangRapidShot) {
            shoot = new GameAction("shoot");
            inputManager.mapToKey(shoot, KeyEvent.VK_SPACE);

            Player player = (Player) map.getPlayer();
            player.setStrength(1);

            // do something here, like give the player points
            soundManager.play(prizeSound);
        } else if (powerUp instanceof PowerUp.WangStrength) {
            shoot = new GameAction("shoot", GameAction.DETECT_INITAL_PRESS_ONLY);
            inputManager.mapToKey(shoot, KeyEvent.VK_SPACE);

            Player player = (Player) map.getPlayer();
            player.setStrength(player.getStrength() * 2);

            POINTS = POINTS + 500;
            // change the music
            soundManager.play(prizeSound);
            toggleDrumPlayback();
        } else if (powerUp instanceof PowerUp.WangGoal) {
            POINTS = POINTS + 1000;
            // advance to next map
            soundManager.play(prizeSound,
                    new EchoFilter(2000, .7f), false);

            midiPlayer.stop();
            Sequence sequence = null;


            if (resourceManager.getLevel() == 3) {
                isFinished = true;
                sequence = midiPlayer.getSequence("sounds/congrats.midi");
            } else {
                map = resourceManager.loadNextMap();
                sequence = midiPlayer.getSequence("sounds/music" + resourceManager.getLevel() + ".midi");
            }

            midiPlayer.play(sequence, true);

        } else {
            Player player = (Player) map.getPlayer();
            if (player.getLife() < 5) {
                player.addLife(1);
            }
        }

        WANGBALLS_COLLECTED += 1;
    }
}
